package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.framePad;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.SwingConstants;

import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.Label;
import xj.graph2d.RectangleShape;
import xj.util.gui.Rectangle2DFloat;

public class BarShape 
  extends RectangleShape {

  public BarShape() {
    this(false, false); 
  }

  public BarShape(boolean filled) {
    this(filled, false);
  }

  public BarShape(boolean filled, boolean createComp) {
    super(filled, createComp); 
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(false);
    setFixedHeight(true);
  }

  public BarShape(int leftEnd, int rightEnd, boolean filled) {
    this(leftEnd, rightEnd, filled, false);
  }

  public BarShape(int leftEnd, int rightEnd,
		  boolean filled, boolean createComp) {
    super(filled, createComp);
    this.leftEnd = leftEnd;
    this.rightEnd = rightEnd;
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(false);
    setFixedHeight(true);
  }

  public BarShape(float x1, float y1, float x2, float y2) {
    this(x1, y1, x2, y2, false, false);
  }

  public BarShape(float x1, float y1, float x2, float y2,
		  boolean filled) {
    this(x1, y1, x2, y2, filled, false);
  }

  public BarShape(float x1, float y1, float x2, float y2,
		  boolean filled, boolean createComp) {
    super(x1, y1, x2, y2, filled, createComp);
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(false);
    setFixedHeight(true);
  }

  public BarShape(float x1, float y1, float x2, float y2,
		  int leftEnd, int rightEnd) {
    this(x1, y1, x2, y2, leftEnd, rightEnd, false, false);
  }

  public BarShape(float x1, float y1, float x2, float y2,
		  int leftEnd, int rightEnd, boolean filled) {
    this(x1, y1, x2, y2, leftEnd, rightEnd, filled, false);
  }

  public BarShape(float x1, float y1, float x2, float y2,
		  int leftEnd, int rightEnd,
		  boolean filled, boolean createComp) {
    super(x1, y1, x2, y2, filled, createComp);
    this.leftEnd = leftEnd;
    this.rightEnd = rightEnd;    
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(false);
    setFixedHeight(true);
  }

  @Override
  public DrawShape makeInstance() {
    return new BarShape(leftEnd, rightEnd, filled, comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof BarShape) {
      BarShape b = (BarShape) s;
      b.leftEnd = leftEnd;
      b.rightEnd = rightEnd;
      b.leftEndSize = leftEndSize;
      b.rightEndSize = rightEndSize;
    }
  }

  @Override
  public String getName() {
    return "Bar";
  }

  public int getLeftEnd() { 
    return leftEnd;
  }

  public void setLeftEnd(int leftEnd) { 
    this.leftEnd = leftEnd;    
  }

  public int getRightEnd() { 
    return rightEnd;
  }

  public void setRightEnd(int rightEnd) { 
    this.rightEnd = rightEnd;    
  }

  public int getLeftEndSize() { 
    return leftEndSize;
  }

  public void setLeftEndSize(int leftEndSize) { 
    this.leftEndSize = leftEndSize;
  }

  public int getRightEndSize() { 
    return rightEndSize;
  }

  public void setRightEndSize(int rightEndSize) { 
    this.rightEndSize = rightEndSize;
  }

  public void positionLabel() {
    Label label = findLabel();
    if (label != null) {
      if (bounds == null) {
	calculateBounds();
      }
      if (label.getBounds() == null) {
	label.calculateBounds();
      }

      float adj1 = Math.max(leftEndSize, nodePad);
      float adj2 = Math.max(rightEndSize, nodePad);
      float x = (float) Math.min(p1.getX(), p2.getX()) + adj1;
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX()) - adj1 - adj2;
      float h = (float) Math.abs(p1.getY() - p2.getY());
      if (labelPosition == LABEL_POSITION_ABOVE_TOP) {
	positionShape(label, x, y, w, h, SwingConstants.TOP, SwingConstants.CENTER, 
		      nodePad, nodePad, false);
      } else if (labelPosition == LABEL_POSITION_LEFT_ADJUST) {
	float hpad = nodePad;
	if (message != null) { 
	  Icon icon = message.getIcon();
	  hpad += (icon.getIconWidth() - framePad); 
	}
	positionShape(label, x, y, w, h, SwingConstants.CENTER, SwingConstants.LEFT, 
		      nodePad, hpad, true);
      } else if (labelPosition == LABEL_POSITION_RIGHT_ADJUST) {
	positionShape(label, x, y, w, h, SwingConstants.TOP, SwingConstants.RIGHT, 
		      nodePad, nodePad, true);
      } else {
	// positionLabel(bounds);
	positionShape(label, x, y, w, h, label.getOffset());
      }
    }
  }

  /*
  @Override
  public int isInside(float px, float py) {
    if (isOnMessage(px, py)) {
      return PointOnMessage;
    }
    return super.isInside(px, py);
  }
  */

  @Override
  public void draw(Graphics2D g2) {
    drawBase(g2, false);
  }

  @Override
  public void drawOutline(Graphics2D g2) {
    drawBase(g2, true);
  }

  public void drawBase(Graphics2D g2, boolean outline) {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    Shape polygon = makePolygon(x, y, w, h);
    outlineShape = polygon;
    if (filled && !outline) {
      fillBackground(g2, x, y, w, h, polygon);
      if (showborder) {
	g2.draw(polygon);
      }
    } else {
      g2.draw(polygon);
    }
  }

  @Override
  public Shape getOutlineShape() {
    if (outlineShape == null) {
      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      outlineShape = makePolygon(x, y, w, h);
    }
    if (outlineShape != null) {
      return outlineShape;
    } else { 
      return super.getBoundingRect();
    }
  }
  
  protected Shape makePolygon(float x, float y, float w, float h) { 
    GeneralPath polygon = new GeneralPath();
    switch (leftEnd) { 
    case TRIANGLE_OUT:
      polygon.moveTo(x + leftEndSize, y);      
      polygon.lineTo(x, y + h / 2);      
      polygon.lineTo(x + leftEndSize, y + h);      
      break;
    case TRIANGLE_IN:
      polygon.moveTo(x, y);      
      polygon.lineTo(x + leftEndSize, y + h / 2);      
      polygon.lineTo(x, y + h);      
      break;
    case WEDGE_OUT:
      polygon.moveTo(x + leftEndSize, y);      
      polygon.lineTo(x, y + h);      
      break;
    case WEDGE_IN:
      polygon.moveTo(x, y);      
      polygon.lineTo(x + leftEndSize, y + h);      
      break;
    case CIRCLE_OUT:
      polygon.moveTo(x + leftEndSize, y);      
      polygon.quadTo(x - 2, y + h / 2, x + leftEndSize, y + h);      
      break; 
    case CIRCLE_IN:       
      polygon.moveTo(x, y);      
      polygon.quadTo(x + leftEndSize + 2, y + h / 2, x, y + h);      
      break;
    case HALF_SQUARE_OUT:
    case HALF_SQUARE_IN:
    case HALF_TRIANGLE_OUT:
    case HALF_TRIANGLE_IN:
    case HALF_CIRCLE_OUT:
    case HALF_CIRCLE_IN:
    case DEFAULT_END:
    default:
      polygon.moveTo(x, y);
      polygon.lineTo(x, y + h);      
    }

    switch (rightEnd) { 
    case TRIANGLE_OUT:
      polygon.lineTo(x + w - rightEndSize, y + h);      
      polygon.lineTo(x + w, y + h / 2);      
      polygon.lineTo(x + w - rightEndSize, y);      
      break; 
    case TRIANGLE_IN:
      polygon.lineTo(x + w, y + h);      
      polygon.lineTo(x + w - rightEndSize, y + h / 2);      
      polygon.lineTo(x + w, y);      
      break;
    case WEDGE_OUT:
      polygon.lineTo(x + w - rightEndSize, y + h);      
      polygon.lineTo(x + w, y + h);      
      break;
    case WEDGE_IN:
      polygon.lineTo(x + w, y + h);      
      polygon.lineTo(x + w - rightEndSize, y + h);      
      break;
    case CIRCLE_OUT:
      polygon.lineTo(x + w - rightEndSize, y + h);      
      polygon.quadTo(x + w + 2, y + h / 2, x + w - rightEndSize, y);      
      break;
    case CIRCLE_IN:       
      polygon.lineTo(x + w, y + h);      
      polygon.quadTo(x + w - rightEndSize - 2, y + h / 2, x + w, y);      
      break;
    case HALF_SQUARE_OUT:
    case HALF_SQUARE_IN:
    case HALF_TRIANGLE_OUT:
    case HALF_TRIANGLE_IN:
    case HALF_CIRCLE_OUT:
    case HALF_CIRCLE_IN:
    case DEFAULT_END:
    default:
       polygon.lineTo(x + w, y + h);      
       polygon.lineTo(x + w, y);      
    }

    polygon.closePath();
    return polygon;
  }

  public float getPreferredHeight() { 
    Rectangle2D b = getSubshapeBounds();
    float height = MIN_HEIGHT;
    if (b != null) { 
      height = Math.max(MIN_HEIGHT, (float) b.getHeight() + 4);
    }
    return height; 
  }

  @Override
  public void doLayout() {
    float x1 = (float) Math.min(p1.getX(), p2.getX());
    float y1 = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p2.getX() - p1.getX());
    float height = getPreferredHeight();
    setEnds(x1, y1, x1 + w, y1 + height);
    positionLabel();
    calculateBounds();
    super.doLayout();
  }

  public Rectangle2D getClipRect() {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    return new Rectangle2DFloat(x + leftEndSize, y, w - leftEndSize - rightEndSize, h);
  }

  public void calculateBounds() {
    calculateBounds(false); // do not include subshapes
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("filled", filled);
    attrs.put("showBorder", showborder);
    attrs.put("leftEnd", leftEnd);
    attrs.put("rightEnd", rightEnd);
    attrs.put("leftEndSize", leftEndSize);
    attrs.put("rightEndSize", rightEndSize);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("filled");
      filled = Boolean.valueOf(s);
      s = node.getAttribute("showBorder");
      showborder = Boolean.valueOf(s);

      s = node.getAttribute("leftEnd");
      try {
	leftEnd = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("rightEnd");
      try {
	rightEnd = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("leftEndSize");
      try {
	leftEndSize = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("rightEndSize");
      try {
	rightEndSize = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
    }
  }

  protected int leftEndSize  = DEFAULT_END_SIZE;
  protected int rightEndSize = DEFAULT_END_SIZE;
  
  protected int leftEnd  = DEFAULT_END;
  protected int rightEnd = DEFAULT_END;

  protected Shape outlineShape = null;

  public static final int DEFAULT_END_SIZE = 8;

  public static final int DEFAULT_END = 100; // flat end 

  public static final int HALF_SQUARE_OUT = 101; 
  public static final int HALF_SQUARE_IN  = 102;

  public static final int TRIANGLE_OUT = 103;
  public static final int TRIANGLE_IN  = 104;

  public static final int HALF_TRIANGLE_OUT = 105;
  public static final int HALF_TRIANGLE_IN  = 106;

  public static final int CIRCLE_OUT = 107;
  public static final int CIRCLE_IN  = 108;  

  public static final int HALF_CIRCLE_OUT = 109;
  public static final int HALF_CIRCLE_IN  = 110;  

  public static final int WEDGE_OUT = 111;
  public static final int WEDGE_IN  = 112;  

  public static final int MIN_HEIGHT = 20; 

  protected static final boolean debug = false;

}
